
use std::fmt::{self, Display, Debug};
use std::boxed::Box;
use serde::ser;
use serde::de;
use std::io;

#[derive(Debug)]
pub enum ErrorCode {
    Message(Box<str>),

    Io(io::Error),

    EofWhileParsingList,
    EofWhileParsingString,
    EofWhileParsingValue,
    EofWhileParsingObject,

    ExpectedEquals,
    ExpectedSomeValue,
    ExpectedListCommaOrEnd,
    ExpectedObjectCommaOrEnd,

    TrailingComma,
    TrailingCharacter,
    InvalidEscape,

    InvalidUtf8Encode,
    InvalidNumber,

    NumberOutOfRange
}
impl PartialEq for ErrorCode {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            ( ErrorCode::Message(s), ErrorCode::Message(o) )=> {
                s == o
            }
            (ErrorCode::Io(e), ErrorCode::Io(o)) => {
                e.kind() == o.kind()
            }
            (ErrorCode::EofWhileParsingList, ErrorCode::EofWhileParsingList) => {
                true
            }
            (ErrorCode::EofWhileParsingString, ErrorCode::EofWhileParsingString) => {
                true
            }
            (ErrorCode::EofWhileParsingValue, ErrorCode::EofWhileParsingValue) => {
                true
            }
            (ErrorCode::EofWhileParsingObject, ErrorCode::EofWhileParsingObject) => {
                true
            }
            (ErrorCode::ExpectedEquals, ErrorCode::ExpectedEquals) => {
                true
            }
            (ErrorCode::ExpectedSomeValue, ErrorCode::ExpectedSomeValue) => {
                true
            }
            (ErrorCode::ExpectedListCommaOrEnd, ErrorCode::ExpectedListCommaOrEnd) => {
                true
            }
            (ErrorCode::ExpectedObjectCommaOrEnd, ErrorCode::ExpectedObjectCommaOrEnd) => {
                true
            }
            (ErrorCode::TrailingComma, ErrorCode::TrailingComma) => {
                true
            }
            (ErrorCode::TrailingCharacter, ErrorCode::TrailingCharacter) => {
                true
            }
            (ErrorCode::InvalidEscape, ErrorCode::InvalidEscape) => {
                true
            }
            (ErrorCode::InvalidUtf8Encode, ErrorCode::InvalidUtf8Encode) => {
                true
            }
            (ErrorCode::InvalidNumber, ErrorCode::InvalidNumber) => {
                true
            }
            (ErrorCode::NumberOutOfRange, ErrorCode::NumberOutOfRange) => {
                true
            }
            (_, _) =>  {
                false
            }
        }
    }
}


impl Display for ErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ErrorCode::Message(msg) => f.write_str(msg),
            ErrorCode::Io(err) => Display::fmt(err, f),
            ErrorCode::EofWhileParsingList => f.write_str("end of file while parsing list"),
            ErrorCode::EofWhileParsingString => f.write_str("end of file while parsing string"),
            ErrorCode::EofWhileParsingValue => f.write_str("end of file while parsing value"),
            ErrorCode::EofWhileParsingObject => f.write_str("end of file while parsing object"),
            ErrorCode::ExpectedEquals => f.write_str("expected equals sign"),
            ErrorCode::ExpectedSomeValue => f.write_str("expected some value"),
            ErrorCode::ExpectedListCommaOrEnd => f.write_str("expected list comma or end"),
            ErrorCode::ExpectedObjectCommaOrEnd => f.write_str("expected object comma or end"),
            ErrorCode::TrailingComma => f.write_str("unexpected comma"),
            ErrorCode::TrailingCharacter => f.write_str("unexpected character"),
            ErrorCode::InvalidEscape => f.write_str("invalid escape"),
            ErrorCode::InvalidUtf8Encode => f.write_str("invalid utf-8 encode"),
            ErrorCode::InvalidNumber => f.write_str("invalid number"),
            ErrorCode::NumberOutOfRange => f.write_str("numnber out of range"),
        }
    }
}







#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Category {
    Io,
    Syntax,
    Data,
    Eof,
}


pub struct Error {
    code: ErrorCode,
    index: usize,
}
pub type Result<T> = std::result::Result<T, Error>;


//creator 
impl Error {
    #[inline]
    fn new(code: ErrorCode, index: usize) -> Self {
        Self {
            code,
            index,
        }
    }

    pub fn eof(code: ErrorCode, index: usize) -> Self {
        Error::new(code, index)
    }
    pub fn syntax(code: ErrorCode, index: usize) -> Self {
        Error::new(code, index)
    }
    #[inline]
    pub fn make_from_str(msg: &str) -> Self {
        Error::new(ErrorCode::Message(msg.to_string().into_boxed_str()), 0)
    }
    #[inline]
    pub fn unimplemented(msg: &str) -> Self {
        Error::make_from_str(msg)
    }

    #[inline]
    pub fn io(err: io::Error) -> Self {
        Error::new(ErrorCode::Io(err), 0)
    }

    pub fn classify(&self) -> Category {
        match self.code  {
            ErrorCode::Message(_) => Category::Data,
            ErrorCode::Io(_) => Category::Io,

            ErrorCode::EofWhileParsingList
                | ErrorCode::EofWhileParsingString
                | ErrorCode::EofWhileParsingValue 
                | ErrorCode::EofWhileParsingObject 
                => Category::Eof,

            ErrorCode::ExpectedEquals 
                | ErrorCode::ExpectedSomeValue
                | ErrorCode::ExpectedListCommaOrEnd
                | ErrorCode::ExpectedObjectCommaOrEnd
                | ErrorCode::TrailingComma
                | ErrorCode::TrailingCharacter
                | ErrorCode::InvalidEscape
                | ErrorCode::InvalidUtf8Encode
                | ErrorCode::InvalidNumber
                | ErrorCode::NumberOutOfRange
                    => Category::Syntax,
        }

    }

    pub fn is_io(&self) -> bool {
        self.classify() == Category::Io
    }
    pub fn is_data(&self) -> bool {
        self.classify() == Category::Data
    }
    pub fn is_eof(&self) -> bool {
        self.classify() == Category::Eof
    }
    pub fn is_syntax(&self) -> bool {
        self.classify() == Category::Syntax
    }

    pub fn code(&self) -> &ErrorCode {
        &self.code
    }
    pub fn index(&self) -> usize {
        self.index
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.code, f)
    }
}
impl Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.index  == 0 {
            write!(f, "error code:{}, index:0", self.code)
        } else {
            write!(f, "error code:{} index:{}", self.code, self.index)
        }
    }
}



// impl for Serde trait Error
impl ser::StdError for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &(self.code)  {
            ErrorCode::Io(err) => err.source(),
            _ => None
        }
    }
}

impl ser::Error for Error {
    fn custom<T: Display>(msg: T) -> Self {
        Error::make_from_str(&format!("{msg}"))
    }
}

impl de::Error for Error {
    fn custom<T: Display> (msg: T) -> Self {
        Error::make_from_str(&format!("{msg}"))
    }
}




#[cfg(test)]
mod test {
    use serde::ser::Error;
    #[test]
    fn use_error(){
        {
            let s = super::Error::make_from_str("This is Error");
            assert_eq!("This is Error", format!("{s}"));
        }
        {
            let s = super::Error::custom("hello world");
            assert_eq!("hello world", format!("{s}"));
        }
    }
    use super::*;
    #[test]
    fn error_code_partial_eq(){
        assert_eq!(ErrorCode::InvalidEscape, ErrorCode::InvalidEscape);
    }
}
